﻿module Analyzer.Main

open Casanova
open Casanova.Core
open Casanova.Utilities
open Casanova.Coroutines
open Casanova.Game
open Casanova.Math
open Casanova.Drawing
open Casanova.StandardLibrary.Core
open Casanova.StandardLibrary.Physics
open VirtualCity.World
open VirtualCity.Main
open VirtualCity.CityDescription
open VirtualCity.City
open VirtualCity.PlanDescription
open VirtualCity.Plan
open VirtualCity.Scripts
open VirtualCity.Scripts.Planner
open Analyzer.Data


let inline (!) x = immediate_lookup x


type UIActions = 
  {
    ClearGraphs   : System.Action
    AddGraph      : System.Action<string, int, int>
    RemoveGraph   : System.Action<string>
    SetGraphData  : System.Action<string, string, seq<string * float>>
  }


let run_analyzer (ui_actions:UIActions) (world:World) =
  let city = world.City
  let city_offset = city.BlockSize * (float32 city.BlocksPerSide) * 0.5f
  let act_cels = Array.init city.BlocksPerSide (fun _ -> Array.init city.BlocksPerSide (fun _ -> ResizeArray([Activity.Travel])))
  let build_cels = Array.init city.BlocksPerSide (fun _ -> Array.init city.BlocksPerSide (fun _ -> ResizeArray()))

  ui_actions.AddGraph.Invoke("Possible_matches", 350, 350);

  let activity_by_name =
    [
      "Groceries",[Activity.Leisure;Activity.Work]
      "Cinema",[Activity.Leisure;Activity.Work]
      "KFC",[Activity.Leisure;Activity.Work]
      "McDonalds",[Activity.Leisure;Activity.Work]
      "Restaurant",[Activity.Leisure;Activity.Work]
      "Gym",[Activity.Leisure;Activity.Work]
      "Factory",[Activity.Work]
      "Home",[Activity.Home]
      "Park",[Activity.Leisure]
    ] |> Map.ofList

  let k = 1
  for b in city.Buildings do 
    let i = ((b.Position.X + city_offset) / city.BlockSize) |> int
    let j = ((b.Position.Y + city_offset) / city.BlockSize) |> int
    for i' = i - k to i + k do
      for j' = j - k to j + k do
        if i' >= 0 && j' >= 0 &&
           i' < city.BlocksPerSide &&
           j' < city.BlocksPerSide then
           let name = !b.Caption.String
           for a in activity_by_name.[name] do
             if act_cels.[i'].[j'] |> Seq.exists ((=) a) |> not then
               act_cels.[i'].[j'].Add(a)
           build_cels.[i'].[j'].Add(b)

  let time_by_activity =
    let time_ranges r =   
      [
        for (s,d) in r do
          for i = 1 to d do
            let t = (s + i) % 24
            yield TimeSpan.FromHours(t |> float)
      ]
    [
      Activity.Home, time_ranges [(23,6); (18,5)]
      Activity.Work, time_ranges [(9,12)]
      Activity.Leisure, time_ranges [(5,3); (11,3); (18,5)]
    ] |> Map.ofList

  // a is true -> confidence = 0
  // a is false -> confidence = 1
  let confidence (l_avg:Vector2<m>, l_var:Vector2<m^2>, l:Vector2<m>, v:Vector2<m/s>, t:DateTime, a:Activity) =
    let t = TimeSpan(t.Hour, t.Minute, t.Second)
    match a with
    | Activity.Home
    | Activity.Work
    | Activity.Leisure -> 
      let velocity_is_small = MathHelper.SmoothStep(0.9f, 0.1f, v.Length / (5.0f<km/h> |> to_meters_per_second))
      let time_slot_is_ok = 
        seq{
          let times = time_by_activity.[a] 
          for t' in times do
            let dt = abs(t'.TotalHours - t.TotalHours) / 2.0
            yield MathHelper.SmoothStep(0.9f, 0.1f, dt |> float32)
        } |> Seq.max
      velocity_is_small * time_slot_is_ok
    | Activity.Travel -> 
      let velocity_is_large = MathHelper.SmoothStep(0.1f, 0.9f, v.Length / (5.0f<km/h> |> to_meters_per_second))
      velocity_is_large
    | _ -> 0.0f

  let analysis_data = 
    {
      ActivitiesGrid =
        {
          Cells     = act_cels
          CellSize  = Vector2.One * city.BlockSize
        }
      BuildingsGrid =
        {
          Cells     = build_cels
          CellSize  = Vector2.One * city.BlockSize
        }
      Agents = ResizeArray()
    }

  let building_clusters = var false

  let log (agent_analysis:AgentAnalysis) (agent:Agent) =
    let alpha = 0.1f

    agent_analysis.PositionAverage <- !agent.Physical.Position
    agent_analysis.PositionVariance <- Vector2.Create(0.0f,0.0f)

    co{
      do! wait(15.0f * 60.0f<s>)
      if !building_clusters |> not then
        let l = !agent.Physical.Position
        let i = ((l.X + city_offset) / city.BlockSize) |> int
        let j = ((l.Y + city_offset) / city.BlockSize) |> int

        do agent_analysis.Observations.Add(
            {
              Location        = !agent.Physical.Position
              Velocity        = !agent.Physical.Velocity
              Time            = !city.Clock.Time
              PossibleMatches = 
                [|
                  for a in analysis_data.ActivitiesGrid.Cells.[i].[j] do
                    let c = confidence(agent_analysis.PositionAverage, agent_analysis.PositionVariance, !agent.Physical.Position, !agent.Physical.Velocity, !city.Clock.Time, a)
                    yield 
                      {
                        Activity    = a
                        Confidence  = c
                      }
                |]
            })
        ui_actions.SetGraphData.Invoke("Possible_matches", "Activity\nconfidence", 
          [
            for i = 0 to (Activity.NumActivities |> int) - 1 do
              let a = i |> enum<Activity>
              match agent_analysis.Observations.[agent_analysis.Observations.Length - 1].PossibleMatches |> Seq.tryFind (fun pm -> pm.Activity = a) with
              | Some pm ->
                yield a.ToString(), pm.Confidence |> float
              | None -> 
                yield a.ToString(), 0.0
            yield "Max",1.0
          ])

      let l = !agent.Physical.Position
      let position_average = agent_analysis.PositionAverage
      let position_variance = agent_analysis.PositionVariance
      let avg_x = alpha * l.X + (1.0f - alpha) * position_average.X
      let avg_y = alpha * l.Y + (1.0f - alpha) * position_average.Y
    
      let var_x = alpha * (l.X - position_average.X) * (l.X - avg_x) + (1.0f - alpha) * position_variance.X
      let var_y = alpha * (l.Y - position_average.Y) * (l.Y - avg_y) + (1.0f - alpha) * position_variance.Y
    
      agent_analysis.PositionAverage  <- Vector2.Create(avg_x, avg_y)
      agent_analysis.PositionVariance <- Vector2.Create(var_x, var_y)
    } |> repeat_


  for agent in !world.City.Agents do 
    let agent_analysis =
      {
        Agent             = agent
        PositionAverage   = Vector2.Zero
        PositionVariance  = Vector2.Zero
        Observations      = ResizeArray()
        Clusters          = ResizeArray()
      }
    do run_ai_script (log agent_analysis agent)

  co{
    do! wait 172800.0f<s> // 2 days
    do building_clusters := true
    // rebuild clusters
    do building_clusters := false
  } |> repeat_

/// Launch the virtual city simulation
let run(clear_graphs   : System.Action,
        add_graph      : System.Action<string, int, int>,
        remove_graph   : System.Action<string>,
        set_graph_data  : System.Action<string, string, seq<string * float>>,
        on_exit:System.Action) = 
  let ui_actions =
    {
      ClearGraphs   = clear_graphs
      AddGraph      = add_graph
      RemoveGraph   = remove_graph
      SetGraphData  = set_graph_data
    }

  let start_game (device,content,exit) =
    let exit () = 
      on_exit.Invoke()
      exit()
    VirtualCity.Main.start_game 1 false (run_analyzer ui_actions) (device,content,exit)
  Casanova.Game.Game<VirtualCity.World.World>.Create(start_game, 1280, 800).Run()
